# mqtt

import time
import machine
from umqtt.simple import MQTTClient
import ubinascii
from machine import Timer

import zz_code
import zz_led
import zz_log

log = zz_log.of()


def of():
    return ZZ_MQTT()


def set_info(host, topic, device_key):
    if not zz_code.str_is_empty(host):
        zz_code.config_set("mqtt_host", host)

    if not zz_code.str_is_empty(topic):
        zz_code.config_set("mqtt_topic", topic)

    if not zz_code.str_is_empty(device_key):
        zz_code.config_set("device_key", device_key)


class ZZ_MQTT():
    def __init__(self):
        # mqtt_host = zz_code.config_get("mqtt_host")
        # mqtt_port = zz_code.config_get("mqtt_port")
        # mqtt_username = zz_code.config_get("mqtt_username")
        # mqtt_password = zz_code.config_get("mqtt_password")
        #
        # client_id = ubinascii.hexlify(machine.unique_id())
        # print(f"mqtt---{mqtt_host}:{mqtt_port}, id: {client_id}, u: {mqtt_username}, p: {mqtt_password}")
        # self.client = MQTTClient(client_id, mqtt_host, port=mqtt_port, user=mqtt_username, password=mqtt_password)
        self.client = None
        #
        self.message_callback = None
        self.connect_ok = False
        self.topic = "siyoumi"
        self.topic_push = "siyoumi/push"

        self.reconnecting = False
        self.enable = True

    def service_close(self):
        log.debug(f"mqtt服务关闭")
        self.enable = False

    def service_open(self):
        log.debug(f"mqtt服务开启")
        self.enable = True

    def service_is(self, enable):
        res = self.enable == enable
        if not enable and res:
            log.debug("mqtt服务已关闭")
        return res

    def set_topic(self, topic):
        self.topic = topic

    # 绑定消息回调事件
    def bind_message_callback(self, func):
        self.message_callback = func
        # 绑定到mqtt
        # self.client.set_callback(self.message_callback_before)

    def message_callback_before(self, topic, msg):
        topic = zz_code.bytes_to_str(topic)
        msg = zz_code.bytes_to_str(msg)
        log.debug(f"message_callback_handle: topic: {topic}, msg: {msg}")

        r = zz_code.json_to_return(msg)
        if self.message_callback is not None:
            self.message_callback(self, r)

    def gen_pwd(self):
        """
        生成mqtt密码
        :return:
        """
        mqtt_username = zz_code.config_get("mqtt_username")
        device_key = zz_code.config_get("device_key")
        rnd_str = str(zz_code.timestamp())
        exp_timestamp = str(zz_code.timestamp() + 300)

        string1 = f"{mqtt_username}{rnd_str}{exp_timestamp}{device_key}"
        sign = zz_code.sha256(string1)
        log.debug(f'string1: {string1}')
        log.debug(f'sign: {sign}')

        return f"{rnd_str};{exp_timestamp};{sign}"

    # 连接
    def connect(self):
        self.connect_ok = False
        try:
            log.debug("开始连接mqtt")
            client_id = zz_code.config_get("mqtt_client_id")
            if zz_code.str_is_empty(client_id):
                client_id = ubinascii.hexlify(machine.unique_id())

            mqtt_host = zz_code.config_get("mqtt_host")
            mqtt_port = zz_code.config_get("mqtt_port")
            mqtt_username = zz_code.config_get("mqtt_username")

            # mqtt_password = zz_code.config_get("mqtt_password")
            mqtt_password = self.gen_pwd()

            log.debug(f"mqtt---{mqtt_host}:{mqtt_port}, id: {client_id}, u: {mqtt_username}, p: {mqtt_password}")
            self.client = MQTTClient(client_id, mqtt_host, port=mqtt_port, user=mqtt_username, password=mqtt_password)
            self.client.set_callback(self.message_callback_before)
            self.connect_ok = self.client.connect()
            if self.connect_ok:
                log.debug(f"订阅主题: {self.topic}, {self.topic_push}")
                self.client.subscribe(self.topic)
                self.client.subscribe(self.topic_push)
            self.connect_ok = True
        except Exception as ex:
            zz_log.of().error(ex)
            self.connect_ok = False
            if str(ex).find('[Errno 104]') != -1:
                # 终止mqtt
                log.error('mqtt连接参数异常')
                pass
        # self.connect_fail()

        if self.connect_ok:
            log.debug(f"mqtt---开启消息接收")
            zz_code.thread_func(self.check_msg)
            log.debug(f"mqtt---开启ping")
            zz_code.thread_func(self.ping)
            zz_led.of().led_dodge_stop()

        return self.connect_ok

    # 接收信息
    def check_msg(self):
        # log.debug(f"mqtt---开启消息接收")
        if self.service_is(False):
            return

        try:
            self.client.check_msg()
        except Exception as ex:
            log.error(f"check_msg error: {ex}")
            self.connect_fail()
            return
        time.sleep(1)
        #
        zz_code.thread_func(self.check_msg)

    # ping 30秒
    def ping(self, run_one=False):
        # log.debug(f"mqtt---开启ping, 执行1次 = {run_one}")
        if self.service_is(False):
            return

        time.sleep(30)
        try:
            self.client.ping()
        except Exception as ex:
            print("ping error:", ex)
            self.connect_fail()
            return

        if run_one:
            pass
        else:
            zz_code.thread_func(self.ping)

    # 连接失败处理
    def connect_fail(self):
        if self.reconnecting:
            log.debug("mqtt重连中")
            return

        self.reconnecting = True

        log.debug("mqtt连接断开")
        zz_led.of().led_dodge(2000)
        log.debug("尝试重连mqtt")
        conn = False
        connect_max = 5
        for i in range(0, connect_max, 1):
            if self.service_is(False):
                break

            conn = self.connect()
            if conn:
                break

            time.sleep(2)
            log.debug(f"mqtt尝试连接: {i}")

        self.reconnecting = False
        if conn:
            # 重连成功
            pass
        else:
            # 防止报死循环，继续重连
            zz_code.thread_func(self.ping, True)

            r = zz_code.of_return(5060, "mqtt连接失败")
            if self.message_callback is not None:
                self.message_callback(self, r)

    def send_msg(self, topic, msg, retain=False, qos=0):
        log.debug(f"{topic}--{msg}")
        try:
            self.client.publish(topic, msg, retain, qos)
        except OSError as ex:
            log.error("send_msg error:", ex)
            self.connect_fail()

    def send_msg_r(self, r: zz_code.Return, topic=None):
        if zz_code.str_is_empty(topic):
            topic = self.topic
        self.send_msg(topic, r.to_json())
